package kuzha.runtime;

import kuzha.ExceptionProvider;
import kuzha.KuzhaObject;

public strictfp class NumberOperator {
	public abstract static class BinaryNumberOperator {
		public final String opname;
		
		private BinaryNumberOperator(String opname) {
			this.opname = opname;
		}
		
		public KuzhaObject calc(double x, double y) {
			throw ExceptionProvider.newBinaryOperation(x, opname, y);
		}
		public KuzhaObject calc(float x, float y) {
			throw ExceptionProvider.newBinaryOperation(x, opname, y);
		}
		public KuzhaObject calc(int x, int y) {
			throw ExceptionProvider.newBinaryOperation(x, opname, y);
		}
		public KuzhaObject calc(long x, long y) {
			throw ExceptionProvider.newBinaryOperation(x, opname, y);
		}
		
		public final KuzhaObject calc(KzNumber left, KzNumber right) {
			if (left instanceof KzInt32) {
				int x = ((KzInt32)left).value;
				if (right instanceof KzInt32)
					return calc(x, ((KzInt32)right).value);
				if (right instanceof KzInt64)
					return calc(x, ((KzInt64)right).value);
				if (right instanceof KzFloat32)
					return calc(x, ((KzFloat32)right).value);
				if (right instanceof KzFloat64)
					return calc(x, ((KzFloat64)right).value);
			}
			else if (left instanceof KzInt64) {
				long x = ((KzInt64)left).value;
				if (right instanceof KzInt32)
					return calc(x, ((KzInt32)right).value);
				if (right instanceof KzInt64)
					return calc(x, ((KzInt64)right).value);
				if (right instanceof KzFloat32)
					return calc(x, ((KzFloat32)right).value);
				if (right instanceof KzFloat64)
					return calc(x, ((KzFloat64)right).value);
			}
			else if (left instanceof KzFloat32) {
				float x = ((KzFloat32)left).value;
				if (right instanceof KzInt32)
					return calc(x, ((KzInt32)right).value);
				if (right instanceof KzInt64)
					return calc(x, ((KzInt64)right).value);
				if (right instanceof KzFloat32)
					return calc(x, ((KzFloat32)right).value);
				if (right instanceof KzFloat64)
					return calc(x, ((KzFloat64)right).value);
			}
			else if (left instanceof KzFloat64) {
				double x = ((KzFloat64)left).value;
				if (right instanceof KzInt32)
					return calc(x, ((KzInt32)right).value);
				if (right instanceof KzInt64)
					return calc(x, ((KzInt64)right).value);
				if (right instanceof KzFloat32)
					return calc(x, ((KzFloat32)right).value);
				if (right instanceof KzFloat64)
					return calc(x, ((KzFloat64)right).value);
			}
			throw ExceptionProvider.newBinaryOperation(left, opname, right);
		}
	}
	
	public abstract static class UnaryNumberOperator {
		public final String opname;
		
		private UnaryNumberOperator(String opname) {
			this.opname = opname;
		}
		
		public KuzhaObject calc(double x) {
			throw ExceptionProvider.newUnaryOperation(x, opname);
		}
		public KuzhaObject calc(float x) {
			throw ExceptionProvider.newUnaryOperation(x, opname);
		}
		public KuzhaObject calc(int x) {
			throw ExceptionProvider.newUnaryOperation(x, opname);
		}
		public KuzhaObject calc(long x) {
			throw ExceptionProvider.newUnaryOperation(x, opname);
		}
		
		public final KuzhaObject calc(KzNumber value) {
			if (value instanceof KzInt32) {
				return calc(((KzInt32)value).value);
			}
			else if (value instanceof KzInt64) {
				return calc(((KzInt64)value).value);
			}
			else if (value instanceof KzFloat32) {
				return calc(((KzFloat32)value).value);
			}
			else if (value instanceof KzFloat64) {
				return calc(((KzFloat64)value).value);
			}
			throw ExceptionProvider.newUnaryOperation(value, opname);
		}
	}
	
	public static final BinaryNumberOperator OP_ADD = new BinaryNumberOperator("+") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x + y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat32.valueOf(x + y); }
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x + y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x + y); }
	};
	
	public static final BinaryNumberOperator OP_SUB = new BinaryNumberOperator("-") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x - y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat32.valueOf(x - y); }
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x - y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x - y); }
	};
	
	public static final BinaryNumberOperator OP_MUL = new BinaryNumberOperator("*") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x * y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat32.valueOf(x * y); }
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x * y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x * y); }
	};
	public static final BinaryNumberOperator OP_DIV = new BinaryNumberOperator("/") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x / y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat32.valueOf(x / y); }
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x / y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x / y); }
	};
	public static final BinaryNumberOperator OP_MOD = new BinaryNumberOperator("%") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x % y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat32.valueOf(x % y); }
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x % y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x % y); }
	};
	public static final BinaryNumberOperator OP_TRUEDIV = new BinaryNumberOperator("\\") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(x / (double)y); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat64.valueOf(x / (double)y); }
		@Override public KuzhaObject calc(int x, int y) { return KzFloat64.valueOf(x / (double)y); }
		@Override public KuzhaObject calc(long x, long y) { return KzFloat64.valueOf(x / (double)y); }
	};
	public static final BinaryNumberOperator OP_POWER = new BinaryNumberOperator("**") {
		@Override public KuzhaObject calc(double x, double y) { return KzFloat64.valueOf(Math.pow(x, y)); }
		@Override public KuzhaObject calc(float x, float y) { return KzFloat64.valueOf(Math.pow(x, y)); }
		@Override public KuzhaObject calc(int x, int y) { return KzFloat64.valueOf(Math.pow(x, y)); }
		@Override public KuzhaObject calc(long x, long y) { return KzFloat64.valueOf(Math.pow(x, y)); }
	};
	
	public static final BinaryNumberOperator OP_BITAND = new BinaryNumberOperator("&") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x & y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x & y); }
	};
	public static final BinaryNumberOperator OP_BITOR = new BinaryNumberOperator("|") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x | y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x | y); }
	};
	public static final BinaryNumberOperator OP_BITXOR = new BinaryNumberOperator("^") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x ^ y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x ^ y); }
	};
	
	public static final BinaryNumberOperator OP_BITSHL = new BinaryNumberOperator("<<") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x << y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x << y); }
	};
	public static final BinaryNumberOperator OP_BITSHR = new BinaryNumberOperator(">>") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x >> y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x >> y); }
	};
	public static final BinaryNumberOperator OP_BITSHRR = new BinaryNumberOperator(">>>") {
		@Override public KuzhaObject calc(int x, int y) { return KzInt32.valueOf(x >>> y); }
		@Override public KuzhaObject calc(long x, long y) { return KzInt64.valueOf(x >>> y); }
	};
	
	public static final BinaryNumberOperator OP_EQ = new BinaryNumberOperator("==") {
		@Override public KuzhaObject calc(double x, double y) { return x == y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(float x, float y) { return x == y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(int x, int y) { return x == y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(long x, long y) { return x == y ? KzBoolean.TRUE : KzBoolean.FALSE; }
	};
	public static final BinaryNumberOperator OP_LT = new BinaryNumberOperator("<") {
		@Override public KuzhaObject calc(double x, double y) { return x < y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(float x, float y) { return x < y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(int x, int y) { return x < y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(long x, long y) { return x < y ? KzBoolean.TRUE : KzBoolean.FALSE; }
	};
	
	public static final BinaryNumberOperator OP_GT = new BinaryNumberOperator(">") {
		@Override public KuzhaObject calc(double x, double y) { return x > y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(float x, float y) { return x > y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(int x, int y) { return x > y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(long x, long y) { return x > y ? KzBoolean.TRUE : KzBoolean.FALSE; }
	};
	public static final BinaryNumberOperator OP_LTEQ = new BinaryNumberOperator("<=") {
		@Override public KuzhaObject calc(double x, double y) { return x <= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(float x, float y) { return x <= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(int x, int y) { return x <= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(long x, long y) { return x <= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
	};
	public static final BinaryNumberOperator OP_GTEQ = new BinaryNumberOperator(">=") {
		@Override public KuzhaObject calc(double x, double y) { return x >= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(float x, float y) { return x >= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(int x, int y) { return x >= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
		@Override public KuzhaObject calc(long x, long y) { return x >= y ? KzBoolean.TRUE : KzBoolean.FALSE; }
	};
	
	public static final UnaryNumberOperator OP_NEGATIVE = new UnaryNumberOperator("-") {
		@Override public KuzhaObject calc(double x) { return KzFloat64.valueOf(-x); }
		@Override public KuzhaObject calc(float x) { return KzFloat32.valueOf(-x);  }
		@Override public KuzhaObject calc(int x) { return KzInt32.valueOf(-x);  }
		@Override public KuzhaObject calc(long x) { return KzInt64.valueOf(-x);  }
	};
	public static final UnaryNumberOperator OP_INC = new UnaryNumberOperator("++") {
		@Override public KuzhaObject calc(double x) { return KzFloat64.valueOf(x + 1); }
		@Override public KuzhaObject calc(float x) { return KzFloat32.valueOf(x + 1);  }
		@Override public KuzhaObject calc(int x) { return KzInt32.valueOf(x + 1);  }
		@Override public KuzhaObject calc(long x) { return KzInt64.valueOf(x + 1);  }
	};
	
	public static final UnaryNumberOperator OP_DEC = new UnaryNumberOperator("--") {
		@Override public KuzhaObject calc(double x) { return KzFloat64.valueOf(x - 1); }
		@Override public KuzhaObject calc(float x) { return KzFloat32.valueOf(x - 1);  }
		@Override public KuzhaObject calc(int x) { return KzInt32.valueOf(x - 1);  }
		@Override public KuzhaObject calc(long x) { return KzInt64.valueOf(x - 1);  }
	};
	
	public static final UnaryNumberOperator OP_ABS = new UnaryNumberOperator("abs") {
		@Override public KuzhaObject calc(double x) { return KzFloat64.valueOf(x < 0 ? -x : x); }
		@Override public KuzhaObject calc(float x) { return KzFloat32.valueOf(x < 0 ? -x : x);  }
		@Override public KuzhaObject calc(int x) { return KzInt32.valueOf(x < 0 ? -x : x);  }
		@Override public KuzhaObject calc(long x) { return KzInt64.valueOf(x < 0 ? -x : x);  }
	};
	
	private NumberOperator() {}
}
